python 属性方法调用 |
您所在的位置:网站首页 › python 属性方法调用 › python 属性方法调用 |
Python 作为动态语言,其一大特性就是对其属性(变量)的动态控制。而这种控制主要表现在 set(赋值)、get(访问)、delete(删除)这三方面。在学习 Python 过程中,也涉及到了大量相关的方法,比如 @.setter 、 __setarri__ 、 __set__ 等等一系列容易混淆的概念,今特作整理,以享。 tip:Python本身内容庞杂,所以这里除了描述符,基本上只涉及到实例属性的控制,类属性、父类属性等尚未完全总结测试。另本文中称特殊方法(special method)为魔术方法。 属性绑定 实例属性添加、删除 首先从定义 Account 开始,我们介绍 Python 类中对属性的动态绑定,并一步步介绍对属性的控制。 class Account(object): def __init__(self, name): self.name = name 在 Python 中,一切皆对象,所有实例都维护一个包含所有实例属性的字典,我们可以通过访问实例的特殊成员 __dict__ 来获取该字典,对实例属性的控制也都会反映到字典中,同样我们也可以通过直接控制字典来控制实例属性。上面我们设计了一个类 Account ,接下来我们对一个实例 a 的属性进行一系列操作,如下: >>> a=Account("xzhang") >>> (a.__dict__)["name"]="xyzhang" >>> a.__dict__ {'name': 'xyzhang'} >>> a.number=1201 >>> a.__dict__ {'name': 'xzhang', 'number': 1201} >>> del a.error AttributeError: error 在实际代码中,我们更倾向于使用 a.number = 1201 ,即 . 操作这种“优雅”的方式来为 a 添加一个新的属性,然而 Python 的解释器会去自动调用魔术方法 __setattr__(self, key, value) ,该方法是为某个属性赋值时解释器去自动调用的,需要注意的是“ 只要是属性被修改或者是赋值,不管这个属性是实例属性、类属性、父类的类属性;亦或者是已经存在的属性、不存在的属性,只要是修改和赋值,都会调用到该方法 ”。当我们想删除这个属性时,同样我们更喜欢使用 del a.number ,实际则会调用魔术方法 __delattr__(self, key) ,需要注意的是“ 该函数只能够删除已经存在的实例属性,对于不存在的属性和类属性是不能够删除的 ”。 这是我们接触到的第一类属性控制方法,但我们往往很少直接使用这种方法,因为有更"优雅"的代码写法。不过作为魔术方法,我们是可以自定义的,比如 一些 log。 __getattr__() 与 __getattrribute__() 这时你可能会发现我们好像遗漏了 get 方法。没错,Python 中确实存在 __getattr__() 方法,但是另有它用,因为访问(get)一个已经存在的属性是一件很自然的事情。 那么在介绍该函数之前,我们先介绍另一个魔术方法 __getattrribute__(self, key) ,该方法就是完成我们上面提到的这个自然的事情——当我们访问一个 已经存在 的属性(也包括方法)时都会 首先自动 调用该方法,因此它还被称作“属性拦截器”。同样它也支持自定义。 接下来我们回过头介绍 __getattr__(self, key) ,该方法是 当用户尝试访问一个根本不存在的属性时 ,来定义类的行为的。比如: def __getattr__(self, key): if key == "id": return 180 if key == "full_number" return self.number*2 else: return "no attrributed" >>> a.id 180 最后补充一下解释器如何查找 object.key 以确定属性不存在,并去调用 __getattr__() 方法的。其查找属性先后顺序为: 特性 实例属性(实例的 __dict__ ) 类属性(类的 __dict__ ) 父类的类属性 如果均查找失败,表示此属性不存在,这才会调用 __getattr__() 方法,如果再次失败则会抛出 AttributeError 异常。如果出现重名属性,解释器则依据顺序访问。 注意无限递归问题 最后,需要提醒的就是在自定义上面的函数时,要注意无效递归问题。如下: def __getattribute__(self, key): if key == "name": print("call __getattribute__()") return self.name else: return super(Account, self).__getattribute__(key) >>> a.name ······ RecursionError: maximum recursion depth exceeded in comparison 代码中我们只是想添加一行打印记录,再返回 self.name ,但是解释器提示“RecursionError”,即无限递归。原因是当我们想返回 self.name 时,解释器会“傻傻地”再次调用 __getattrribute__ 方法,造成递归调用。而如果我们不显式地返回值,系统将为我们返回 None 。办法如代码中 else: 段所示,我们去调用父类中的同名方法便是,让父类替我们去完成,因为我们知道所有用户自定义的类都有一个默认的父类—— object 。 内省方法 getattr() , setattr() , delattr() 我们再引入一套新的属性管理手段。这三个内省方法共同组成了一套控制属性 set、get、delete 的手段。 getattr(object, key[, default]) 获取对象 object 中 key 属性的值,如果不存在,则返回默认值 default 。 setattr(object, key, value) 给对象 object 的属性 key 赋值 value ,如果属性不存在,先创建再赋值。 delattr(object, key) 删除对象 object 的属性 key ,如果属性 key 不存在,则报 AttributeError 异常。 我们同样需要注意其无限递归问题,如下代码: class Account(object): def __init__(self, name): self.name = name def __setattr__(self, key, value): if key == "number": print("key.number call __setattr__") setattr(key, value) else: print("call __setattr__") super(Account, self).__setattr__(key, value) >>> a=Account("xzhang") call __setattr__ >>> a.name="xyzhang" call __setattr__ >>> a.number=12 ······ RecursionError: maximum recursion depth exceeded while calling a Python object 当解释器检测到没有 number 属性并调用 __setattr__ 方法,当运行到 setattr() 时,解释器也会递归调用 __setattr__ 方法。另外代码中也演示了在初始化实例过程中,当代码运行到 self.name = name 时,也会调用 __setattr__() 方法,因此一定要注意在引入上面讲到的魔术方法后 self.name 的使用。 属性控制方式 以上分别介绍了两种控制属性的手段,一套是魔术方法,一套是内省方法。然而 Python 设计者认为这样的 Python 还不够“优雅”。主要有以下两个痛点: 当我们想单独控制类内的某一个固定属性时,使用 if key == "name:" 这种方式不够“优雅”。 当我们想设计一套控制属性的方法,并拿来在同一类的不同属性甚至是多个类中复用时,Python 还缺少一种“优雅”的调用手段。 特性(property) 于是为了解决第一个问题,特性作为一种特殊的属性被提了出来,包括了: @property 作为 get 方法。 @name.setter 作为 set 方法,将其中 name 改写为你要控制的属性名 name,而非 key(即非字符串不包括引号)。 @name.deleter 作为 delete 方法,其中 name 与上同理。 只要在类中包含以上三个被装饰器修饰的方法(或者只有第一个方法),则具备了特性功能,这里给出设计和使用特性的关键代码如下: @property def name(self): pass @name.setter def name(self,value): pass @name.deleter def name(self): pass >>> a.name # 调用property name()方法 ······ >>> a.name="xzhang" # 调用setter name(a, "xzhang") >>> del name # 调用deleter name(a) 可以看到特性的实现手法是利用了类装饰器,但是要注意其被提高到了类似于关键字的层面,即你不需要 import 任何东西便可以使用它们。 实际上特性的使用更多的是为了遵循统一访问原则,即当我们想访问实例属性时我们虽然通过 = 赋值符号,但是其内部自动调用了被装饰的方法,从而减少了()函数作用符号的使用,使得代码更加“优雅”的同时,实现了对内部数据的封装。 描述符(descriptor) 为了解决第二个问题,Python 引入了一个新的功能————描述符(官方文档翻译为描述器)。其作用就是将一种自定义的属性控制机制单独抽象出来使用。在 Python 中我们规定,如果在一个类中给定义了魔术方法 __get__ 、 __set__ 、 __delete__ 中的任意一个,那么我们就可以将这个类称之为描述符。对应这三个魔术方法: __get__(self, instance, cls) self 描述符的实例 instance 使用描述符的那个类的实例 cls 使用描述符的那个类 __set__(self, instance,value) __delete__(self, instance) 这里给出创建和使用描述符的关键代码: class Mydescriptor(): def __init__(self, name, type, default=None): self.name = "_" + name self.type = type self.default = default if default else type() def __get__(self, instance, cls): print("call __get__()") return getattr(instance, self.name, self.default) def __set__(self, instance, value): print("call __set__()") setattr(instance, self.name, value) def __delete__(self, instance): print("call __delete__()") class Foo(): def __init__(self, name): self.m_name = Mydescriptor(name, str, " ") g_name = Mydescriptor("xzhang", str, " ") a = Foo("xzhang") my_name = a.m_name my_name = a.g_name # 隐式调用Foo.name.__get__(a,Foo) a.g_name = "xyzhang" # 隐式调用Foo.name.__set__(a,"xzhang") del a.g_name # 隐式调用Foo.name.__delete__(a) 如上所示,需要注意的一点是: “描述符只能在类级别上进行实例化,不能通过在 __init__() 和其他方法中创建描述符对象来为一个实例创建描述符” ,换句话说,描述符所创建的实例一定是类属性,而不是实例属性。也就是说代码中 my_name = a.m_name 实际并不会调用 __get__() 方法。 tip:类装饰器属性可以通过类名和实例名访问,但是测试发现混用时可能发生无法自动调用对应魔术方法的情况。 总结我们介绍了一共四种属性控制手段,包括魔术方法、内省函数、特性、描述符,以及一个 __getattribute__() 方法。实际上我们还同时给自己留了一个坑:特性属性、描述符属性与普通的实例属性、类属性的访问优先级,虽然正常情况,我们应该避免重名情况的发生;以及众多魔术方法自动调用顺序问题,精力有限,待以后再填。 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |